﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="FieldsTab.cs" company="Marcelo Roca">
//   Copyright (c) 2011 Marcelo Roca. All rights reserved.
// </copyright>
// <author name="Marcelo Roca" email="marce@marceloroca.com"/>
// <license>
//   This program is free software: you can redistribute it and/or modify
//   it under the terms of the GNU General Public License as published by
//   the Free Software Foundation, either version 3 of the License, or
//   (at your option) any later version.
// 
//   This program is distributed in the hope that it will be useful,
//   but WITHOUT ANY WARRANTY; without even the implied warranty of
//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   GNU General Public License for more details.
//
//   You should have received a copy of the GNU General Public License
//   along with this program.  If not, see http://www.gnu.org/licenses/.
// </license>
// --------------------------------------------------------------------------------------------------------------------

namespace GameKits.Unity.SmartInspector.Editor
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;

    using GameKits.Unity.SmartInspector.Editor.Serializer;

    using UnityEditor;

    using UnityEngine;

    /// <summary>
    /// Tab para mostrar campos Field o Property
    /// </summary>
    internal class FieldsTab : ITab
    {
        /// <summary>
        /// Lista de items que va a mostrar
        /// </summary>
        private readonly IEnumerable<SerializedItem> listItems;

        /// <summary>
        /// El objeto target que está editando
        /// </summary>
        private readonly UnityEngine.Object target;

        /// <summary>
        /// Arreglo de colores
        /// </summary>
        private static readonly Color[] ColorArray = new[]
            {
                Color.black,
                Color.blue,
                Color.cyan,
                Color.gray,
                Color.green,
                Color.grey,
                Color.magenta,
                Color.red,
                Color.white,
                Color.yellow
            };

        /// <summary>
        /// Initializes a new instance of the <see cref="FieldsTab"/> class.
        /// </summary>
        /// <param name="listItems">
        /// The list items.
        /// </param>
        /// <param name="target">
        /// The target.
        /// </param>
        public FieldsTab(IEnumerable<SerializedItem> listItems, UnityEngine.Object target)
        {
            this.listItems = listItems;
            this.target = target;
        }

        /// <summary>
        /// Reallocates an array with a new size, and copies the contents
        /// of the old array to the new array.
        /// </summary>
        /// <param name="oldArray">
        /// the old array, to be reallocated.
        /// </param>
        /// <param name="newSize">
        /// the new array size.
        /// </param>
        /// <returns>
        /// A new array with the same contents.
        /// </returns>
        public static Array ResizeArray(Array oldArray, int newSize)
        {
            var oldSize = oldArray.Length;
            var elementType = oldArray.GetType().GetElementType();

            if (elementType != null)
            {
                var newArray = Array.CreateInstance(elementType, newSize);
                var preserveLength = Math.Min(oldSize, newSize);
                if (preserveLength > 0)
                {
                    Array.Copy(oldArray, newArray, preserveLength);
                }

                return newArray;
            }

            return oldArray;
        }

        /// <summary>
        /// Realiza las operaciones GUI para mostrar los fields o propiedaes de la lista
        /// </summary>
        public void Draw()
        {
            if (this.listItems == null)
            {
                return;
            }

            this.DrawItems(this.listItems);
        }

        /// <summary>
        /// Draw sinlge item
        /// </summary>
        /// <param name="item">
        /// The item to be drawed.
        /// </param>
        private void DrawItem(SerializedItem item)
        {
            var guiEnabled = GUI.enabled;

            if ((item.Attribute != null && item.Attribute.Disabled) || item.Disabled)
            {
                GUI.enabled = true;
            }

            switch (item.MemberType)
            {
                case SerializedItem.ItemTypes.Method:
                    if (GUILayout.Button(item.Label))
                    {
                        Debug.Log(DateTime.Now + " Button " + item.Label);
                        try
                        {
                            item.Invoke();
                        }
                        catch (Exception ex)
                        {
                            Debug.LogError(ex.Message, this.target);
                        }
                    }

                    break;

                case SerializedItem.ItemTypes.Field:
                case SerializedItem.ItemTypes.Property:
                    switch (item.ValueType)
                    {
                        case SerializedItem.ValueTypes.Array:
                            this.DrawArraySelector(item);
                            break;

                        case SerializedItem.ValueTypes.ArrayList:
                            Debug.LogError("ArrayList is not implemented, please use System.Array type.");
                            break;

                        case SerializedItem.ValueTypes.String:
                            this.DrawTextSelector(item);
                            break;

                        case SerializedItem.ValueTypes.PasswordString:
                            this.DrawPasswordSelector(item);
                            break;

                        case SerializedItem.ValueTypes.TagString:
                            this.DrawTagSelector(item);
                            break;

                        case SerializedItem.ValueTypes.Vector2:
                            this.DrawVector2Selector(item);
                            break;

                        case SerializedItem.ValueTypes.Vector3:
                            this.DrawVector3Selector(item);
                            break;

                        case SerializedItem.ValueTypes.Vector4:
                            this.DrawVector4Selector(item);
                            break;

                        case SerializedItem.ValueTypes.Rect:
                            this.DrawRectSelector(item);
                            break;

                        case SerializedItem.ValueTypes.AnimationCurve:
                            this.DrawAnimationCurveSelector(item);
                            break;

                        case SerializedItem.ValueTypes.Float:
                            this.DrawFloatSelector(item);
                            break;

                        case SerializedItem.ValueTypes.Int:
                            this.DrawIntSelector(item);
                            break;

                        case SerializedItem.ValueTypes.LayerInt:
                            this.DrawLayerSelector(item);
                            break;

                        case SerializedItem.ValueTypes.Bool:
                            this.DrawBoolSelector(item);
                            break;

                        case SerializedItem.ValueTypes.Color:
                            this.DrawColorSelector(item);
                            break;

                        case SerializedItem.ValueTypes.UnityEngineObject:
                            this.DrawObjectSelector(item);
                            break;

                        case SerializedItem.ValueTypes.SerializedClass:
                            this.DrawSerializedSelector(item);
                            break;

                        case SerializedItem.ValueTypes.Enum:
                            this.DrawEnumSelector(item);
                            break;

                        default:
                            Debug.LogError(DateTime.Now + " Type: '" + item.SystemType + "' not supported");
                            break;
                    }

                    break;

                default:
                    Debug.Log(DateTime.Now + " otro " + item.Label.text);
                    break;
            }

            GUI.enabled = guiEnabled;
        }

        /// <summary>
        /// pitnamos las lista de items
        /// </summary>
        /// <param name="list">
        /// la lista de miembros a pintar
        /// </param>
        private void DrawItems(IEnumerable<SerializedItem> list)
        {
            foreach (var item in list)
            {
                this.DrawItem(item);
            }
        }

        /// <summary>
        /// The draw bool.
        /// </summary>
        /// <param name="memberItem">
        /// The member Item.
        /// </param>
        private void DrawBoolSelector(SerializedItem memberItem)
        {
            try
            {
                var valp = (bool)memberItem.GetValue();
                var val = EditorGUILayout.Toggle(memberItem.Label, valp);

                if (val != valp && (memberItem.Attribute == null || !memberItem.Attribute.ReadOnly))
                {
                    memberItem.SetValue(val);
                }
            }
            catch (Exception ex)
            {
                Debug.LogError(ex.Message, this.target);
            }
        }

        /// <summary>
        /// The draw enum.
        /// </summary>
        /// <param name="memberItem">
        /// The member Item.
        /// </param>
        private void DrawEnumSelector(SerializedItem memberItem)
        {
            try
            {
                var valp = (Enum)memberItem.GetValue();
                var val = EditorGUILayout.EnumPopup(memberItem.Label, valp);

                if (val != valp && (memberItem.Attribute == null || !memberItem.Attribute.ReadOnly))
                {
                    memberItem.SetValue(val);
                }
            }
            catch (Exception ex)
            {
                Debug.LogError(ex.Message, this.target);
            }
        }

        /// <summary>
        /// Draws a Color selector
        /// </summary>
        /// <param name="memberItem">
        /// The member Item.
        /// </param>
        private void DrawColorSelector(SerializedItem memberItem)
        {
            try
            {
                var valp = (Color)memberItem.GetValue();
                var val = EditorGUILayout.ColorField(memberItem.Label, valp);

                if (val != valp && (memberItem.Attribute == null || !memberItem.Attribute.ReadOnly))
                {
                    memberItem.SetValue(val);
                }
            }
            catch (ExitGUIException)
            {
            }
            catch (Exception ex)
            {
                Debug.LogError(DateTime.Now + " DrawColorSelector (" + Event.current + ")\n\r" + ex.Message, this.target);
            }
        }

        /// <summary>
        /// Draws a Object selector
        /// </summary>
        /// <param name="memberItem">
        /// The member Item.
        /// </param>
        private void DrawObjectSelector(SerializedItem memberItem)
        {
            try
            {
                var valp = (UnityEngine.Object)memberItem.GetValue();
                var val = EditorGUILayout.ObjectField(memberItem.Label, valp, memberItem.SystemType);

                if (val != valp && (memberItem.Attribute == null || !memberItem.Attribute.ReadOnly))
                {
                    memberItem.SetValue(val);
                }
            }
            catch (Exception ex)
            {
                Debug.LogError(ex.Message, this.target);
            }
        }

        /// <summary>
        /// Draws a Float selector
        /// </summary>
        /// <param name="memberItem">
        /// The member Item.
        /// </param>
        private void DrawFloatSelector(SerializedItem memberItem)
        {
            try
            {
                var valp = (float)memberItem.GetValue();

                float val;

                if (memberItem.Attribute != null && memberItem.Attribute is BrowsableNumericAttribute)
                {
                    var attr = (BrowsableNumericAttribute)memberItem.Attribute;

                    if (attr.ShowSlider)
                    {
                        val = EditorGUILayout.Slider(
                            memberItem.Label, valp, attr.Min, attr.Max);
                    }
                    else
                    {
                        val = Mathf.Clamp(
                            EditorGUILayout.FloatField(memberItem.Label, valp),
                            attr.Min,
                            attr.Max);
                    }
                }
                else
                {
                    val = EditorGUILayout.FloatField(memberItem.Label, valp);
                }

                if (val != valp && (memberItem.Attribute == null || !memberItem.Attribute.ReadOnly))
                {
                    memberItem.SetValue(val);
                }
            }
            catch (Exception ex)
            {
                Debug.LogError(ex.Message, this.target);
            }
        }

        /// <summary>
        /// Draws a Float selector
        /// </summary>
        /// <param name="memberItem">
        /// The member Item.
        /// </param>
        private void DrawIntSelector(SerializedItem memberItem)
        {
            try
            {
                var valp = (int)memberItem.GetValue();

                int val;

                if (memberItem.Attribute != null && memberItem.Attribute is BrowsableNumericAttribute)
                {
                    var attr = (BrowsableNumericAttribute)memberItem.Attribute;

                    if (attr.ShowSlider)
                    {
                        val =
                            (int)
                            EditorGUILayout.Slider(
                                memberItem.Label, valp, (int)attr.Min, (int)attr.Max);
                    }
                    else
                    {
                        val = Mathf.Clamp(
                            EditorGUILayout.IntField(memberItem.Label, valp),
                            (int)attr.Min,
                            (int)attr.Max);
                    }
                }
                else
                {
                    val = EditorGUILayout.IntField(memberItem.Label, valp);
                }

                if (val != valp && (memberItem.Attribute == null || !memberItem.Attribute.ReadOnly))
                {
                    memberItem.SetValue(val);
                }
            }
            catch (Exception ex)
            {
                Debug.LogError(ex.Message, this.target);
            }
        }

        /// <summary>
        /// Draws a Float selector
        /// </summary>
        /// <param name="memberItem">
        /// The member Item.
        /// </param>
        private void DrawLayerSelector(SerializedItem memberItem)
        {
            try
            {
                var valp = (int)memberItem.GetValue();
                var val = EditorGUILayout.LayerField(memberItem.Label, valp);

                if (val != valp && (memberItem.Attribute == null || !memberItem.Attribute.ReadOnly))
                {
                    memberItem.SetValue(val);
                }
            }
            catch (Exception ex)
            {
                Debug.LogError(ex.Message, this.target);
            }
        }

        /// <summary>
        /// Draws a text selector
        /// </summary>
        /// <param name="memberItem">
        /// The member Item.
        /// </param>
        private void DrawTextSelector(SerializedItem memberItem)
        {
            try
            {
                var valp = (string)memberItem.GetValue() ?? string.Empty;
                var val = EditorGUILayout.TextField(memberItem.Label, valp);

                if (val != valp && (memberItem.Attribute == null || !memberItem.Attribute.ReadOnly))
                {
                    memberItem.SetValue(val);
                }
            }
            catch (Exception ex)
            {
                Debug.LogError(ex.Message, this.target);
            }
        }

        /// <summary>
        /// Draws a text selector
        /// </summary>
        /// <param name="memberItem">
        /// The member Item.
        /// </param>
        private void DrawPasswordSelector(SerializedItem memberItem)
        {
            try
            {
                var valp = (string)memberItem.GetValue();
                var val = EditorGUILayout.PasswordField(memberItem.Label, valp);

                if (val != valp && (memberItem.Attribute == null || !memberItem.Attribute.ReadOnly))
                {
                    memberItem.SetValue(val);
                }
            }
            catch (Exception ex)
            {
                Debug.LogError(ex.Message, this.target);
            }
        }

        /// <summary>
        /// Draws a text selector
        /// </summary>
        /// <param name="memberItem">
        /// The member Item.
        /// </param>
        private void DrawTagSelector(SerializedItem memberItem)
        {
            try
            {
                var valp = (string)memberItem.GetValue();
                var val = EditorGUILayout.TagField(memberItem.Label, valp);

                if (val != valp && (memberItem.Attribute == null || !memberItem.Attribute.ReadOnly))
                {
                    memberItem.SetValue(val);
                }
            }
            catch (Exception ex)
            {
                Debug.LogError(ex.Message, this.target);
            }
        }

        /// <summary>
        /// Draws a vector2 selector
        /// </summary>
        /// <param name="memberItem">
        /// The member Item.
        /// </param>
        private void DrawVector2Selector(SerializedItem memberItem)
        {
            try
            {
                var valp = (Vector2)memberItem.GetValue();
                var val = EditorGUILayout.Vector2Field(memberItem.Label.text, valp);

                if (val != valp && (memberItem.Attribute == null || !memberItem.Attribute.ReadOnly))
                {
                    memberItem.SetValue(val);
                }
            }
            catch (Exception ex)
            {
                Debug.LogError(ex.Message, this.target);
            }
        }

        /// <summary>
        /// Draws a vector3 selector
        /// </summary>
        /// <param name="memberItem">
        /// The member Item.
        /// </param>
        private void DrawVector3Selector(SerializedItem memberItem)
        {
            try
            {
                var valp = (Vector3)memberItem.GetValue();
                var val = EditorGUILayout.Vector3Field(memberItem.Label.text, valp);

                if (val != valp && (memberItem.Attribute == null || !memberItem.Attribute.ReadOnly))
                {
                    memberItem.SetValue(val);
                }
            }
            catch (Exception ex)
            {
                Debug.LogError(ex.Message, this.target);
            }
        }

        /// <summary>
        /// Draws a vector4 selector
        /// </summary>
        /// <param name="memberItem">
        /// The member Item.
        /// </param>
        private void DrawVector4Selector(SerializedItem memberItem)
        {
            try
            {
                var valp = (Vector4)memberItem.GetValue();
                var val = EditorGUILayout.Vector4Field(memberItem.Label.text, valp);

                if (val != valp && (memberItem.Attribute == null || !memberItem.Attribute.ReadOnly))
                {
                    memberItem.SetValue(val);
                }
            }
            catch (Exception ex)
            {
                Debug.LogError(ex.Message, this.target);
            }
        }

        /// <summary>
        /// Draws a rect selector
        /// </summary>
        /// <param name="memberItem">
        /// The member Item.
        /// </param>
        private void DrawRectSelector(SerializedItem memberItem)
        {
            try
            {
                var valp = (Rect)memberItem.GetValue();
                var val = EditorGUILayout.RectField(memberItem.Label, valp);

                if (val != valp && (memberItem.Attribute == null || !memberItem.Attribute.ReadOnly))
                {
                    memberItem.SetValue(val);
                }
            }
            catch (Exception ex)
            {
                Debug.LogError(ex.Message, this.target);
            }
        }

        /// <summary>
        /// Draws a AnimationCurve selector
        /// </summary>
        /// <param name="memberItem">
        /// The member Item.
        /// </param>
        private void DrawAnimationCurveSelector(SerializedItem memberItem)
        {
            try
            {
                var valp = (AnimationCurve)memberItem.GetValue();

                AnimationCurve val;

                if (memberItem.Attribute is BrowsableAnimationCurveAttribute)
                {
                    var attr = (BrowsableAnimationCurveAttribute)memberItem.Attribute;
                    val = EditorGUILayout.CurveField(memberItem.Label, valp ?? new AnimationCurve(), ColorArray[(int)attr.Color], attr.Range);
                }
                else
                {
                    val = EditorGUILayout.CurveField(memberItem.Label, valp ?? new AnimationCurve());
                }

                if (!memberItem.Attribute.ReadOnly && val != valp)
                {
                    memberItem.SetValue(val);
                }
            }
            catch (ExitGUIException)
            {
            }
            catch (Exception ex)
            {
                Debug.LogError(DateTime.Now + " DrawAnimationCurveSelector (" + Event.current + ")\n\r" + ex.Message, this.target);
            }
        }

        /// <summary>
        /// The draw bool.
        /// </summary>
        /// <param name="memberItem">
        /// The member Item.
        /// </param>
        private void DrawSerializedSelector(SerializedItem memberItem)
        {
            memberItem.BoolTool = EditorGUILayout.Foldout(memberItem.BoolTool, memberItem.Label);

            if (memberItem.BoolTool)
            {
                EditorGUILayout.BeginHorizontal();
                GUILayout.Space(15);
                EditorGUILayout.BeginVertical();

                try
                {
                    // buscamos todos las propiedades del taget
                    var t = memberItem.SystemType;

                    var list1 = (from info in t.GetFields(BindingFlags.Public | BindingFlags.Instance)
                                 where
                                    info.GetCustomAttributes(true).OfType<NonSerializedAttribute>().Count() == 0
                                 select
                                     new SerializedItem(info, memberItem.GetValue(), null)).ToList();

                    var list2 = (from info in t.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                                 where
                                     info.GetCustomAttributes(true).OfType<NonSerializedAttribute>().Count() == 0
                                     && info.CanRead
                                 select
                                     new SerializedItem(info, memberItem.GetValue(), null)).ToList();

                    this.DrawItems(list1.Union(list2));
                }
                catch (Exception err)
                {
                    Debug.LogError(DateTime.Now + err.Message);
                }

                EditorGUILayout.EndVertical();
                EditorGUILayout.EndHorizontal();
            }
        }

        /// <summary>
        /// Mostramos un editor de arreglo
        /// </summary>
        /// <param name="memberItem">
        /// The member item.
        /// </param>
        private void DrawArraySelector(SerializedItem memberItem)
        {
            try
            {
                var valp = (Array)memberItem.GetValue();
                memberItem.BoolTool = EditorGUILayout.Foldout(memberItem.BoolTool, memberItem.Label);

                if (memberItem.BoolTool)
                {
                    EditorGUILayout.BeginHorizontal();
                    GUILayout.Space(15);
                    EditorGUILayout.BeginVertical();

                    var size = valp.Length;

                    if (memberItem.IntTool == int.MinValue)
                    {
                        memberItem.IntTool = size;
                    }

                    if (memberItem.Attribute != null && memberItem.Attribute is BrowsableArrayAttribute)
                    {
                        var attr = (BrowsableArrayAttribute)memberItem.Attribute;

                        if (attr.ShowSlider)
                        {
                            memberItem.IntTool = (int)EditorGUILayout.Slider("Size", memberItem.IntTool, 0, attr.Max);
                            Debug.Log(DateTime.Now + " DrawArraySelector with slider max=" + attr.Max);
                        }
                        else
                        {
                            memberItem.IntTool = Mathf.Clamp(EditorGUILayout.IntField("Size", memberItem.IntTool), 0, attr.Max);
                        }
                    }
                    else
                    {
                        memberItem.IntTool = Mathf.Clamp(EditorGUILayout.IntField("Size", memberItem.IntTool), 0, BrowsableArrayAttribute.MaxSize);
                    }

                    if (memberItem.IntTool != size)
                    {
                        EditorGUILayout.BeginHorizontal();

                        if (GUILayout.Button("Resize"))
                        {
                            var arr = ResizeArray(valp, memberItem.IntTool);
                            memberItem.SetValue(arr);
                        }

                        if (GUILayout.Button("Cancel"))
                        {
                            memberItem.IntTool = size;
                        }

                        GUILayout.FlexibleSpace();

                        EditorGUILayout.EndHorizontal();
                    }

                    EditorGUILayout.Separator();

                    var guiEnabled = GUI.enabled;

                    if (size > 0)
                    {
                        var innerType = valp.GetType().GetElementType();

                        for (var i = 0; i < size; i++)
                        {
                            memberItem.Disabled = i >= memberItem.IntTool;
                            GUI.enabled = !memberItem.Disabled;

                            var mi = new SerializedItem(valp, i, "Element " + i, innerType);
                            this.DrawItem(mi);
                        }
                    }

                    GUI.enabled = false;

                    for (var i = size; i < memberItem.IntTool; i++)
                    {
                        EditorGUILayout.LabelField("Element " + i, string.Empty);
                    }

                    GUI.enabled = guiEnabled;

                    EditorGUILayout.EndVertical();
                    EditorGUILayout.EndHorizontal();
                }
            }
            catch (Exception ex)
            {
                Debug.LogError(DateTime.Now + " " + ex.Message, this.target);
            }
        }
    }
}
